﻿using DataTransfer.Core.Models;
using DataTransfer.Core.Providers;
using DataTransfer.SDK;
using DataTransfer.SDK.Events;
using DataTransfer.SDK.Models;
using MiniFox;
using MiniFox.Caching;
using MiniFox.Components;
using MiniFox.Data;
using MiniFox.Infrastructure;
using MiniFox.Platform.Applications.Auth;
using MiniFox.Platform.File.Models;
using MiniFox.Resources;
using MiniFox.Utility;
using NPOI.SS.UserModel;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;

namespace DataTransfer.Core.Services
{
    /// <summary>
    /// 
    /// </summary>
    [AutoResolve(COMPONENT_NAME)]
    public class ExcelImportEngine : ImportEngine, IExcelImportEventListener
    {
        public const string COMPONENT_NAME = "excel";
        const string DELETED_ROWS = "deletedRows";
        const string UPDATED_ROWS = "updatedRows";
        /// <summary>
        /// 
        /// </summary>
        public event EventHandler<ImportBeforeExcelLoadEventArgs> BeforeExcelLoad;
        /// <summary>
        /// 
        /// </summary>
        public event EventHandler<ImportAfterExcelLoadEventArgs> AfterExcelLoad;
        /// <summary>
        /// 
        /// </summary>
        public event EventHandler<ImportExcelFillTableEventArgs> TableFilling;
        /// <summary>
        /// 
        /// </summary>
        public event EventHandler<ImportExcelFillTableEventArgs> TableFilled;
        /// <summary>
        /// 
        /// </summary>
        public event EventHandler<ImportExcelFillRowEventArgs> RowFilling;
        /// <summary>
        /// 
        /// </summary>
        public event EventHandler<ImportExcelFillRowEventArgs> RowFilled;

        bool _initialized;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="importerName"></param>
        /// <param name="authStateManagement"></param>
        public ExcelImportEngine(string importerName, IAuthStateManagement authStateManagement) : base(importerName, authStateManagement)
        {
            _initialized = false;
        }
        /// <summary>
        /// 
        /// </summary>
        [AutoWired(COMPONENT_NAME)]
        protected override IImportConfigProvider Configuration { get; set; }
        /// <summary>
        /// 
        /// </summary>
        protected internal new IExcelImporter Importer
        {
            get
            {
                return base.Importer as IExcelImporter;
            }
        }

        [InitInvoke]
        private void Initialize()
        {
            if (Importer == null)
                return;
            if (_initialized)
                return;

            foreach (var module in Importer.Modules)
            {
                var moduleType = module.Value;
                var type = typeof(IExcelImportModule);
                if (!type.IsAssignableFrom(moduleType))
                {
                    string msg = string.Format(CommonMessages.MSG_CommonTypeUnmatched, moduleType.FullName, type.FullName);
                    var exception = new InvalidOperationException(msg);
                    WriteWarnLog(exception.Message, exception);
                    continue;
                }

                var instance = Activator.CreateInstance(moduleType) as IExcelImportModule;

                instance.Init(this);
            }
            _initialized = true;
        }

        #region 读取excel到数据集
        private IWorkbook GetWorkbook(FileData fileData)
        {
            IWorkbook workbook;
            using (var stream = new MemoryStream(fileData.Data))
            {
                if (fileData.ExtensionName == ".xlsx")
                    workbook = new NPOI.XSSF.UserModel.XSSFWorkbook(stream);
                else if (fileData.ExtensionName == ".xls")
                    workbook = new NPOI.HSSF.UserModel.HSSFWorkbook(stream);
                else
                    throw new MiniFox.Exceptions.SystemException("您上传的文件格式错误，只能上传Excel文件(.xls或.xlsx)，请检查。");

                stream.Close();
            }
            return workbook;
        }

        /// <summary>
        /// 根据表配置，设置dataTable
        /// </summary>
        /// <param name="dataTable"></param>
        /// <param name="excelImportSheet"></param>
        /// <returns></returns>
        protected virtual DataTable SetDataTable(DataTable dataTable, IExcelImportSheet excelImportSheet)
        {
            excelImportSheet.Fields.ForEach(field =>
            {
                var dataColumn = dataTable.Columns.Add(field.FieldName, field.Type);
                SetDataColumn(dataColumn, field);
            });
            return dataTable;
        }

        /// <summary>
        /// 根据字段配置，设置dataColumn
        /// </summary>
        /// <param name="dataColumn"></param>
        /// <param name="field"></param>
        protected virtual void SetDataColumn(DataColumn dataColumn, IExcelImportSheetField field)
        {
            dataColumn.Caption = field.DisplayName;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="dataTable"></param>
        /// <param name="sheet"></param>
        /// <param name="excelImportSheet"></param>
        /// <returns></returns>
        protected virtual SummaryModel FillDataTable(DataTable dataTable, ISheet sheet, IExcelImportSheet excelImportSheet)
        {
            var summary = new SummaryModel();
            summary.Name = dataTable.TableName;
            int rowCount = 0;
            int successCount = 0;
            for (int i = excelImportSheet.StartRowIndex; i <= sheet.LastRowNum; i++)
            {
                IRow row = sheet.GetRow(i);
                rowCount++;
                if (row == null)
                    continue;
                try
                {
                    var dataRow = dataTable.NewRow();
                    FillDataRow(dataRow, row, excelImportSheet);
                    dataTable.Rows.Add(dataRow);
                    dataRow.AcceptChanges();
                    successCount++;
                }
                catch
                {
                    continue;
                }
            }
            summary.TotalCount = rowCount;
            summary.SuccessCount = successCount;
            summary.FailCount = rowCount - successCount;
            return summary;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="dataRow"></param>
        /// <param name="row"></param>
        /// <param name="excelImportSheet"></param>
        protected virtual void FillDataRow(DataRow dataRow, IRow row, IExcelImportSheet excelImportSheet)
        {
            var e = new ImportExcelFillRowEventArgs(excelImportSheet, row, dataRow);
            OnRowFilling(e);
            excelImportSheet.Fields.ForEach(field =>
            {
                ICell cell = row.GetCell(field.Ordinal, field.MissingCellPolicy);
                dataRow[field.FieldName] = GetFieldValue(cell, field);
            });
            OnRowFilled(e);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="cell"></param>
        /// <param name="field"></param>
        /// <returns></returns>
        protected virtual object GetFieldValue(ICell cell, IExcelImportSheetField field)
        {
            object val = null;
            try
            {
                val = ExcelTransferUtility.GetCellValue(cell);
                if (val != null && val != DBNull.Value)
                {
                    val = Convert.ChangeType(val, field.Type);
                }
            }
            catch //(Exception e)
            {
            }
            return val;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="dataSet"></param>
        /// <param name="workbook"></param>
        /// <returns></returns>
        public SummaryModel[] FillDataFromExcel(DataSet dataSet, IWorkbook workbook)
        {
            if (dataSet == null)
            {
                return null;
            }
            List<SummaryModel> summaries = new List<SummaryModel>();
            Importer.Sheets.ForEach(importSheet =>
            {
                var sheet = workbook.GetSheet(importSheet.SheetName);
                if (sheet == null)
                    return;
                var dataTable = new XDataTable(importSheet.SheetName, ImporterName);
                dataSet.Tables.Add(dataTable);
                SetDataTable(dataTable, importSheet);
                var e = new ImportExcelFillTableEventArgs(importSheet, sheet, dataTable);
                OnTableFilling(e);
                var summary = FillDataTable(dataTable, sheet, importSheet);
                OnTableFilled(e);

                summaries.Add(summary);
            });
            return summaries.ToArray();
        }
        #endregion


        protected internal override FileData GetTemplate()
        {
            FileData fileData = new ();
            fileData.FileName = $"{this.ImporterName}.xlsx";
            fileData.FileId = SequenceGUID.NewGUID();
            fileData.Group = COMPONENT_NAME;
            using (var stream = new MemoryStream())
            {
                IWorkbook workbook = new NPOI.XSSF.UserModel.XSSFWorkbook(stream);
                this.Importer.Sheets.ForEach(sheetItem =>
                {
                    var sheet = workbook.CreateSheet(sheetItem.SheetName);
                    int headerRowCount = sheetItem.HeaderRowIndex;
                    IRow headRow = sheet.CreateRow(sheetItem.HeaderRowIndex);

                    int i = sheetItem.StartRowIndex;
                    sheetItem.Fields.ForEach(fieldItem =>
                    {
                        ICell headCell = headRow.CreateCell(i++, CellType.String);
                        headCell.SetCellValue(string.IsNullOrEmpty(fieldItem.DisplayName) ? fieldItem.FieldName : fieldItem.DisplayName);

                    });
                });

                fileData.Data = stream.ToArray();
                stream.Close();
            }
            return fileData;
        }

        #region preload
        /// <summary>
        /// 
        /// </summary>
        /// <param name="fileData"></param>
        /// <returns></returns>
        protected override ImportGroup PreloadGroup(FileData fileData)
        {
            var workbook = GetWorkbook(fileData);
            var dataSet = new DataSet(fileData.FileId);
            OnBeforeExcelLoad(new ImportBeforeExcelLoadEventArgs(Importer, workbook, dataSet));
            var summaries = FillDataFromExcel(dataSet, workbook);
            OnAfterExcelLoad(new ImportAfterExcelLoadEventArgs(Importer, workbook, dataSet, summaries));
            ImportGroup importGroup = new ()
            {
                File = fileData,
                OriginalData = dataSet,
                OriginalSummary = summaries
            };
            return importGroup;
        }

        #endregion

        #region preview

        /// <summary>
        /// 
        /// </summary>
        /// <param name="importGroup"></param>
        /// <param name="request"></param>
        /// <returns></returns>
        protected override object PreviewData(ImportGroup importGroup, PagedQueryObject<PreviewRequest> request)
        {
            if (importGroup.OriginalData is not DataSet dataSet) return null;
            var preview = request.Parameters;
            var dataTable = dataSet.Tables[preview.Name];
            var resultSet = dataTable.GetDataResult(request);
            return resultSet;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="importGroup"></param>
        /// <param name="request"></param>
        protected override object UndoData(ImportGroup importGroup, UndoRequest request)
        {
            if (importGroup.OriginalData is not DataSet dataSet) return null;

            var dataTable = dataSet.Tables[request.Name];

            var rowIndex = request.RowIndex;
            var obj = dataTable.UndoRow(rowIndex);
            return new
            {
                State = RowViewState.Unchanged,
                Data = obj
            };
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="importGroup"></param>
        /// <param name="request"></param>
        protected override void UndoAllData(ImportGroup importGroup, UndoAllRequest request)
        {
            if (importGroup.OriginalData is not DataSet dataSet) return ;

            var dataTable = dataSet.Tables[request.Name];
            dataTable.UndoAllChanges();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="importGroup"></param>
        /// <param name="request"></param>
        protected override object RemoveData(ImportGroup importGroup, RemoveRequest request)
        {
            if (importGroup.OriginalData is not DataSet dataSet) return null;

            var dataTable = dataSet.Tables[request.Name];
            var obj = dataTable.DeleteRow(request.RowIndex);
            return new
            {
                State = RowViewState.Removed,
                Data = obj
            };
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="importGroup"></param>
        /// <param name="request"></param>
        protected override object UpdateData(ImportGroup importGroup, UpdateRequest request)
        {
            if (importGroup.OriginalData is not DataSet dataSet) return null;

            var dataTable = dataSet.Tables[request.Name];
            var obj = dataTable.UpdateRow(request.RowIndex, request.Data);
            return new
            {
                State = RowViewState.Changed,
                Data = obj
            };
        }
        #endregion

        #region events

        protected virtual void OnBeforeExcelLoad(ImportBeforeExcelLoadEventArgs e)
        {
            BeforeExcelLoad?.Invoke(this, e);
        }

        protected virtual void OnAfterExcelLoad(ImportAfterExcelLoadEventArgs e)
        {
            AfterExcelLoad?.Invoke(this, e);
        }

        protected virtual void OnTableFilling(ImportExcelFillTableEventArgs e)
        {
            TableFilling?.Invoke(this, e);
        }

        protected virtual void OnTableFilled(ImportExcelFillTableEventArgs e)
        {
            TableFilled?.Invoke(this, e);
        }

        protected virtual void OnRowFilling(ImportExcelFillRowEventArgs e)
        {
            RowFilling?.Invoke(this, e);
        }

        protected virtual void OnRowFilled(ImportExcelFillRowEventArgs e)
        {
            RowFilled?.Invoke(this, e);
        }

        #endregion

        #region static members
        ///// <summary>
        ///// 
        ///// </summary>
        ///// <param name="dataTable"></param>
        ///// <param name="queryRequest"></param>
        ///// <returns></returns>
        //public static PagedResultObject<PreviewTable> GetPagedResult(DataTable dataTable, IPagedQueryObject queryRequest)
        //{
        //    var table = CreateTableSchema(dataTable);
        //    SetTableData(table, dataTable, queryRequest);
        //    var resultObject = new PagedResultObject<PreviewTable>(dataTable.Rows.Count, queryRequest.PageIndex, queryRequest.PageSize);
        //    resultObject.Result = table;
        //    return resultObject;
        //}
        ///// <summary>
        ///// 
        ///// </summary>
        ///// <param name="dataTable"></param>
        ///// <returns></returns>
        //public static PreviewTable CreateTableSchema(DataTable dataTable)
        //{
        //    PreviewTable table = new(dataTable.TableName);
        //    foreach (DataColumn col in dataTable.Columns)
        //    {
        //        PreviewTableColumn column = new PreviewTableColumn
        //        {
        //            Name = col.ColumnName,
        //            DisplayName = col.Caption,
        //            Ordinal = col.Ordinal,
        //            Type = col.DataType.ToString()
        //        };
        //        table.Columns.Add(column);
        //    }
        //    return table;
        //}
        //private static DataRow[] GetDataRows(DataTable dataTable, IQueryObject queryRequest)
        //{
        //    DataRow[] dataRows;
        //    if (queryRequest == null)
        //        dataRows = dataTable.Select(null, null, DataViewRowState.OriginalRows);
        //    else
        //    {
        //        var filterExpression = queryRequest.GetQueryExpression();
        //        var sortExpression = CreateSortExpression(queryRequest.SortFields);

        //        dataRows = dataTable.Select(filterExpression, sortExpression, DataViewRowState.OriginalRows);
        //    }
        //    return dataRows;
        //}
        //private static string CreateSortExpression(IDictionary<string, bool> sortFields)
        //{
        //    var sortExpression = string.Empty;

        //    if (sortFields != null && sortFields.Count > 0)
        //    {
        //        var sortExps = new List<string>();
        //        foreach (var sortItem in sortFields)
        //        {
        //            var sortExp = $"{sortItem.Key} {(sortItem.Value ? "DESC" : "ASC")}";
        //            sortExps.Add(sortExp);
        //        }
        //        sortExpression = string.Join(",", sortExps);
        //    }
        //    return sortExpression;
        //}
        //private static void SetTableData(PreviewTable table, DataTable dataTable, IPagedQueryObject queryRequest)
        //{
        //    var dataRows = GetDataRows(dataTable, queryRequest);
        //    int startRowIndex = Tools.GetStartIndex(queryRequest.PageSize, queryRequest.PageIndex);
        //    int endRowIndex = Tools.GetEndIndex(queryRequest.PageSize, queryRequest.PageIndex, dataTable.Rows.Count);

        //    for (var i = startRowIndex; i <= endRowIndex && i < dataRows.Length; i++)
        //    {
        //        var dataRow = dataRows[i];
        //        var row = table.NewRow();
        //        row.RowIndex = i;
        //        table.Rows.Add(row);

        //        if (dataTable.ExtendedProperties[DELETED_ROWS] is Dictionary<int, IDictionary<string, object>> deletedRows && deletedRows.ContainsKey(i))
        //        {
        //            row.State = RowViewState.Removed;
        //            foreach (PreviewTableColumn column in table.Columns)
        //            {
        //                row[column.Name] = deletedRows[i][column.Name];
        //            }
        //        }
        //        else
        //        {
        //            if (dataTable.ExtendedProperties[UPDATED_ROWS] is Dictionary<int, IDictionary<string, object>> updatedRows && updatedRows.ContainsKey(i))
        //            {
        //                row.State = RowViewState.Changed;
        //            }
        //            foreach (PreviewTableColumn column in table.Columns)
        //            {
        //                row[column.Name] = dataRow[column.Name];
        //            }
        //        }
        //    }
        //}
        #endregion
    }
}
